# -*- coding: utf-8 -*-
"""
用于服务器端的端口复用
将服务器A端口的流量，根据特征分配到服务器其他服务的端口
同时返回服务器流量回客户端
"""
import socket          # 提供底层的 BSD Socket API
import threading       # 用多线程实现并发
import configparser    # 读取 *.ini 配置文件（Python 2 叫 ConfigParser）

# 创建 ConfigParser 实例，用来解析 config.ini
conf = configparser.ConfigParser()
print(type(conf))           # 打印 <class 'configparser.ConfigParser'>，说明 conf 是一个“类”的实例
conf.read('config1.0.ini')     # 把磁盘上的 config.ini 读进内存，后续可通过 conf.get(section, key) 取值

# 下面四个变量是“默认”值；如果 config.ini 里有同名配置，实际运行时会覆盖
LISTEN_IP   = '0.0.0.0'      # 代理监听地址，0.0.0.0 表示本机所有网卡
LISTEN_PORT = 12345          # 代理监听端口
BACKEND_IP  = '127.0.0.1'    # 后端真实服务 IP
BACKEND_PORT = 25565         # 后端真实服务端口
Feature   = '3000464d4c0063dd02'  # 特征串，用来判断首包是否合法（十六进制字符串）

# -------------- 工具函数：把一端的数据原封不动“管道”到另一端 --------------
def pipe(src, dst):
    """
    把 src socket 收到的所有字节流透传给 dst socket。
    任意一端关闭或出错都会退出循环，并关闭两端 socket。
    """
    try:
        while True:                 # 持续转发直到 EOF
            buf = src.recv(4096)    # 一次最多收 4 KB
            if not buf:             # 返回空串表示对端已关闭
                break
            dst.sendall(buf)        # 保证全部发出去（sendall 会循环 send）
    except socket.error:            # 出现 TCP 错误时静默结束
        pass
    finally:                        # 无论正常结束还是异常，都要关闭两端
        src.close()
        dst.close()

# -------------- 处理单个客户端连接的线程函数 --------------
def handle(cli_sock, cli_addr):
    """
    每个新连接都会启动一个独立线程执行此函数。
    1. 先读首包，检查是否包含“特征串”；
    2. 如果匹配就把后续流量桥接到后端；不匹配就丢弃。
    """
    try:
        first = cli_sock.recv(1024)             # 读取客户端发来的首包，最多 1 KB
        # 长度不足 9 字节 或 末尾 9 字节里找不到特征串，则视为非法
        if len(first) < 9 or str(first[-9:]).find(Feature) != -1:
            print("{} 不匹配，交付{}端口处理".format(cli_addr, LISTEN_PORT))
            # cli_sock.close()  # 原注释掉的代码，可见作者曾想过直接关闭
            return                                      # 直接结束线程，客户端会收到 TCP RST

        # 如果包含特征串，则视为合法流量，准备桥接
        print("{} 匹配，桥接到 {}:{}".format(cli_addr, BACKEND_IP, BACKEND_PORT))
        be_sock = socket.create_connection((BACKEND_IP, BACKEND_PORT))  # 主动连后端
        be_sock.sendall(first)                                          # 把首包先发过去

        # 启动两条“管道”线程，实现全双工转发
        threading.Thread(target=pipe, args=(cli_sock, be_sock)).start()  # 客户端 → 后端
        threading.Thread(target=pipe, args=(be_sock, cli_sock)).start()  # 后端 → 客户端
    except Exception as e:
        print("异常: {}".format(e))   # 打印异常信息
        cli_sock.close()            # 关闭客户端 socket，防止句柄泄漏

# -------------- 主函数：监听端口、接受连接 --------------
def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建 TCP socket
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 地址复用，方便调试快速重启
    srv.bind((LISTEN_IP, LISTEN_PORT))                      # 绑定监听地址和端口
    srv.listen(128)                                         # 开始监听，最大挂起连接队列 128
    print("代理已启动，监听 {}:{} → {}:{}".format(
          LISTEN_IP, LISTEN_PORT, BACKEND_IP, BACKEND_PORT))

    # 主循环：阻塞等待新连接，每来一个就创建线程处理
    while True:
        cli, addr = srv.accept()                              # accept() 返回 (新套接字, 对端地址)
        threading.Thread(target=handle, args=(cli, addr)).start()  # 把 handle 函数跑在独立线程里

# -------------- Python 入口判断 --------------
if __name__ == '__main__':
    main()   # 当本文件被直接 python proxy_py2.py 执行时才跑 main()